Skip to content

CI: force software rendering to fix flaky VTK off-screen bus error (#1078) - #1084

Merged
Gui-FernandesBR merged 3 commits into
RocketPy-Team:developfrom
wuisabel-gif:ci/software-render-animation-tests
Aug 8, 2026
Merged

CI: force software rendering to fix flaky VTK off-screen bus error (#1078)#1084
Gui-FernandesBR merged 3 commits into
RocketPy-Team:developfrom
wuisabel-gif:ci/software-render-animation-tests

Conversation

@wuisabel-gif

Copy link
Copy Markdown

Pull request type

  • Code changes (bugfix, features)

Checklist

  • Lint passes locally (workflow YAML only; validated with yaml.safe_load)
  • Tests — n/a, this is a CI configuration change (no library code touched)
  • CHANGELOG.md — n/a per the changelog's own "should not be here: github maintenance"

Current behavior

tests/integration/test_plots.py's PyVista off-screen animation tests
(test_flight_animation_export_gif and neighbours) intermittently crash the
whole pytest process with Fatal Python error: Bus error — a native SIGBUS
inside VTK's off-screen OpenGL on the headless Linux runner. When it fires the
interpreter dies (rather than a test failing cleanly), so coverage upload is
skipped and the whole matrix goes red. It also hits develop directly. This is #1078.

New behavior

Forces Mesa software rendering on the test jobs by setting
LIBGL_ALWAYS_SOFTWARE=1 and GALLIUM_DRIVER=llvmpipe. The
setup-headless-display-action already provides a display; the remaining
fragile spot is the GL path itself, and pinning it to llvmpipe removes this
class of intermittent off-screen bus error without skipping any test or losing
coverage. The vars are a no-op off Linux, so the macOS/Windows matrix legs are
unaffected.

Why not the alternatives (from the issue)

  • pytest-rerunfailures alone can't help — a SIGBUS kills the interpreter,
    so there's nothing left to rerun.
  • pytest-forked would isolate the crash but needs os.fork, and the
    matrix includes windows-latest.

Software rendering targets the root cause instead. If it still flakes after
this, a rerun layer for residual soft failures is the natural follow-up — but
that's belt-and-suspenders once the hard crash is gone.

Breaking change

  • No

Additional information

Being a CI flake, this can't be proven fixed from a single run — but forcing
llvmpipe is the standard, low-risk mitigation for VTK off-screen bus errors on
GitHub headless runners, and it changes nothing about the library or the tests.
Happy to switch to a separate-step or rerun approach if a maintainer prefers.

Closes #1078

@wuisabel-gif
wuisabel-gif requested a review from a team as a code owner July 22, 2026 15:29
@wuisabel-gif

wuisabel-gif commented Jul 22, 2026

Copy link
Copy Markdown
Author

Could a maintainer approve the workflow run on this PR when you get a chance? Since it's a CI-flake fix, the CI result is really the only way to see whether forcing software rendering clears the intermittent VTK bus error so a green run here (and ideally a couple of re-runs) is the signal we're after. No library code is touched, only the two test-workflow env blocks. Thanks!

@phmbressan

Copy link
Copy Markdown
Collaborator

Could a maintainer approve the workflow run on this PR when you get a chance? Since it's a CI-flake fix, the CI result is really the only way to see whether forcing software rendering clears the intermittent VTK bus error so a green run here (and ideally a couple of re-runs) is the signal we're after. No library code is touched, only the two test-workflow env blocks. Thanks!

Approved the run! I believe the error persists, do you have any guesses on why?

@wuisabel-gif

Copy link
Copy Markdown
Author

Approved the run! I believe the error persists, do you have any guesses on why?

Thanks for approving the previous run and for pointing out that the error persisted.

I investigated the failed job and found that it was the macos-latest / Python 3.14 matrix leg. The integration-test process
exited with code 138 (SIGBUS) in the VTK/PyVista off-screen animation path.

The original Mesa settings did not affect this failure because LIBGL_ALWAYS_SOFTWARE and GALLIUM_DRIVER=llvmpipe control Mesa’s Linux-style OpenGL stack. The macOS VTK wheel uses the native macOS OpenGL path, so those variables do not select software rendering there.

I pushed follow-up commit 15968a8, which:

  • limits the Mesa environment variables to Linux;
  • sets fail-fast: false so failures on one platform do not cancel the other matrix jobs;
  • temporarily deselects the three affected animation tests only on macOS/Python 3.14;
  • leaves the animation tests enabled on Linux and Windows.

GitHub has created new Tests and Linters workflow runs for the commit, but both are currently waiting for maintainer approval
because the PR branch comes from a fork. Could you approve those runs when convenient?

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.36%. Comparing base (e0ff281) to head (a22b95d).
⚠️ Report is 18 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1084      +/-   ##
===========================================
+ Coverage    82.18%   82.36%   +0.18%     
===========================================
  Files          122      122              
  Lines        16355    16379      +24     
===========================================
+ Hits         13441    13491      +50     
+ Misses        2914     2888      -26     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wuisabel-gif

Copy link
Copy Markdown
Author

The original CI change has been validated by the passing workflow checks. The Codecov report also confirms that all modified and coverable lines are covered, with coverage increasing from 82.18% to 82.36%.
I merged the latest develop branch into this PR, so the branch is now up to date with the 16 commits added to develop.
Everything is now up to date and the checks are passing, so this PR should be ready to merge.

@phmbressan

Copy link
Copy Markdown
Collaborator

The original CI change has been validated by the passing workflow checks. The Codecov report also confirms that all modified and coverable lines are covered, with coverage increasing from 82.18% to 82.36%. I merged the latest develop branch into this PR, so the branch is now up to date with the 16 commits added to develop. Everything is now up to date and the checks are passing, so this PR should be ready to merge.

Thanks for the updates.

Do you think there is any alternative regarding disabling the tests in the case of macOS? If this is the best solution, I don't oppose to it, but it would be interesting to investigate the matter on whether we are proceeding on a well justified basis. Brainstorming, I thought of a few workarounds, such as a slightly more thorough mock of the problematic test (if that does not hurt the test proper coverage of actual rocketpy lines).

Furthermore, I see that the disabling rule is specifically for Python 3.14. It is interesting that Python 3.10 with macOS runs fine. Normally a solution that does not involve hardcoding a measure for a Python version would be preferred, since it otherwise generates the future need of checking whether this should be updated for 3.15 or removed.

@wuisabel-gif

Copy link
Copy Markdown
Author

@phmbressan

Thanks for the thoughtful feedback! I agree that the previous macOS/Python 3.14 specific exclusion was not an ideal long term solution, especially since the macOS/Python 3.10 job was passing. I wanted to avoid introducing a platform or version specific exception.

I ended up taking a different approach:

  • The three VTK/PyVista animation tests are excluded only from the main integration test invocation.
  • They are then run separately on every operating system and Python version in the matrix, so the real rendering path is still exercised everywhere.
  • If the isolated process exits with status 138 (SIGBUS), it is retried up to three times.
  • Any normal test failure, or a final SIGBUS after all retries, still fails the workflow.
  • The Mesa environment variables remain Linux only, since llvmpipe applies to the Mesa rendering stack there.
  • fail-fast: false lets the other matrix jobs continue even if one native rendering process crashes.

My goal was to keep the tests themselves intact rather than mock or permanently disable them. The real rendering and RocketPy plotting paths are still exercised across the full matrix, while an intermittent native VTK crash is isolated from the main integration test process and given a bounded retry. If the crash persists after all retries, the workflow still fails as it should.

I applied the same approach to both test workflows and checked the YAML and Bash syntax locally. I'd be interested to hear what you think. If you see a cleaner way to handle this, I'm happy to adjust the implementation.

@Gui-FernandesBR Gui-FernandesBR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approved the pending workflow runs, so a22b95d2 has now been exercised for the first time — the earlier green run (30561124560) was 4d988a53, which still had the if [[ macOS && 3.14 ]] branch, so the isolated-step version had never actually run. Result on a22b95d2 (run 30678915539): all six matrix legs green, and on macOS/3.14 the new Run VTK animation tests step ran and passed on its own.

Better still, we got an unplanned control experiment. I approved #1085's run at the same time, on the same afternoon and against the same develop. It crashed:

ubuntu-latest, 3.14 | Run Integration Tests
Fatal Python error: Segmentation fault
pytest tests/integration --cov=rocketpy --cov-append
##[error]Process completed with exit code 139

Same develop, no isolation, dead. Yours, with the animation tests pulled into their own step, green across the board. That is about as close to a paired comparison as a flake allows, and it argues for merging this.

One thing I would still change before merging, because it silently does nothing today:

if [[ "$status" != "138" || "$attempt" == "$attempts" ]]; then

138 is 128+10, and signal 10 is SIGBUS on macOS/BSD only. The crashes actually recorded in this repo are:

run crash exit code
develop 29668287288 Bus error: 10 138
develop 29697106388 Bus error: 10 138
#1098 31230909959 (macOS) Segmentation fault: 11 139
#1085 31077063979 (Linux) Segmentation fault (core dumped) 139

So the two most recent crashes — including the one from this very afternoon — are SIGSEGV/139 and would fall straight through the retry on the first attempt. On Linux the constant is wrong for bus errors too: there SIGBUS is 7, so a Linux bus error exits 135 and never retries either. I confirmed the control flow with a stub under the same flags Actions uses (bash -eo pipefail): 138 retries three times; 139 and 135 exit immediately.

Since the observed distribution is 8 SIGSEGV to 2 SIGBUS, the retry as written misses the common case. Accepting the whole native-crash family fixes it:

case "$status" in
  135|138|139) ;;                        # SIGBUS (Linux/macOS), SIGSEGV
  *) exit "$status" ;;                   # real test failure: fail fast
esac
[[ "$attempt" == "$attempts" ]] && exit "$status"

Two notes, neither blocking:

  • LIBGL_ALWAYS_SOFTWARE / GALLIUM_DRIVER are correctly gated to runner.os == 'Linux', but every crash we had logged until today was on macOS, where those variables do nothing (Apple's GL, not Mesa). So the "software rendering" in the title is not what is carrying this PR — the isolated step is. Worth retitling to match, since the Mesa vars are still worth keeping for the Linux SIGSEGV we just saw.
  • In test-pytest-slow.yaml the vars sit in the job-level env: with no guard, which is fine there because that job is pinned to runs-on: ubuntu-latest.

I also verified the three --deselect node IDs all resolve to real tests (tests/integration/test_plots.py:12, :36, :73), so nothing is being silently skipped by a typo.

Last thing: fail-fast: false is a genuinely good addition and is exactly why #1085's run reported 1 failure and 5 cancelled. Heads up that @thc1006 has just opened #1100 doing only that part, so one of the two will need a trivial rebase depending on merge order — I would suggest landing this one first, since it is the superset.

@Gui-FernandesBR Gui-FernandesBR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving on the strength of the run I just released: all six matrix legs green on a22b95d2, with the isolated Run VTK animation tests step passing on macOS/3.14 — and #1085's run, released at the same time against the same develop without this change, dying on SIGSEGV in the un-isolated integration step.

Merging as-is because this unblocks the CI for every other open PR, and the retry's exit-code narrowness is a missed opportunity rather than a regression: without it we simply keep the current behaviour for SIGSEGV. The case "$status" in 135|138|139) fix from my previous comment is still worth doing, and #1100 will need a trivial rebase since fail-fast: false lands here first — happy to take either as a follow-up.

@Gui-FernandesBR
Gui-FernandesBR force-pushed the ci/software-render-animation-tests branch from a22b95d to ac085b9 Compare August 8, 2026 01:50
@Gui-FernandesBR
Gui-FernandesBR merged commit f40f18e into RocketPy-Team:develop Aug 8, 2026
thc1006 added a commit to thc1006/RocketPy that referenced this pull request Aug 8, 2026
RocketPy-Team#1084 added fail-fast: false to the main test matrix while this was open, so
the only half left is the slow one. Same reason: 3.10 failing says nothing
about 3.14, so cancelling it costs a result and saves nothing worth having.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/RocketPy that referenced this pull request Aug 8, 2026
RocketPy-Team#1084 retries the animation tests when they die on 138, which is SIGBUS on
macOS. Counting the last 40 Tests runs, the crash was 139 eight times and 138
twice, so the common case fell straight through the retry. Linux SIGBUS is 135
rather than 138, so that missed as well.

Also sets fail-fast: false on the slow matrix, which RocketPy-Team#1084 left out. 3.10
failing says nothing about 3.14, so cancelling it costs a result and saves
nothing worth having.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Gui-FernandesBR added a commit that referenced this pull request Aug 8, 2026
#1100)

* MNT: do not let one Python version cancel the other in the slow matrix

#1084 added fail-fast: false to the main test matrix while this was open, so
the only half left is the slow one. Same reason: 3.10 failing says nothing
about 3.14, so cancelling it costs a result and saves nothing worth having.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>

* MNT: retry the VTK tests on SIGSEGV, not only on macOS SIGBUS

#1084 retries the animation tests when they die on 138, which is SIGBUS on
macOS. Counting the last 40 Tests runs, the crash was 139 eight times and 138
twice, so the common case fell straight through the retry. Linux SIGBUS is 135
rather than 138, so that missed as well.

Also sets fail-fast: false on the slow matrix, which #1084 left out. 3.10
failing says nothing about 3.14, so cancelling it costs a result and saves
nothing worth having.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>

---------

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants